package com.vhall.component.service.redpacket.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.crypto.digest.MD5;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.vhall.component.dao.redpacket.RedPacketMapper;
import com.vhall.component.dao.redpacket.RedPacketRecordMapper;
import com.vhall.component.entity.notice.vo.RedPacketNoticeVo;
import com.vhall.component.entity.order.OrderDetailEntity;
import com.vhall.component.entity.pay.vo.PaymentParamVO;
import com.vhall.component.entity.rebroadcast.RedPacketEntity;
import com.vhall.component.entity.rebroadcast.RedPacketRecordEntity;
import com.vhall.component.entity.rebroadcast.RedPacketSettingEntity;
import com.vhall.component.entity.redpacket.vo.*;
import com.vhall.component.entity.room.dto.RoomJoinsEntityDTO;
import com.vhall.component.entity.room.dto.RoomsEntityDTO;
import com.vhall.component.framework.common.constant.CommonConstant;
import com.vhall.component.framework.common.exception.BusinessException;
import com.vhall.component.framework.common.utils.CollectionUtils;
import com.vhall.component.plugin.common.constant.HttpConstant;
import com.vhall.component.plugin.common.exception.BizErrorCode;
import com.vhall.component.plugin.common.support.VssToken;
import com.vhall.component.plugin.common.support.VssTokenThreadLocal;
import com.vhall.component.plugin.common.utils.JsonUtil;
import com.vhall.component.plugin.common.utils.RandomUtil;
import com.vhall.component.plugin.core.service.PaasService;
import com.vhall.component.plugin.core.service.event.PaasMessageEvent;
import com.vhall.component.service.notice.NoticesService;
import com.vhall.component.service.order.OrderService;
import com.vhall.component.service.pay.NotifyService;
import com.vhall.component.service.pay.PaymentService;
import com.vhall.component.service.redpacket.common.RedpacketConstants;
import com.vhall.component.service.redpacket.service.CacheService;
import com.vhall.component.service.redpacket.service.RedPacketRecordService;
import com.vhall.component.service.redpacket.service.RedPacketService;
import com.vhall.component.service.redpacket.service.RedPacketSettingService;
import com.vhall.component.service.room.RoomInnerService;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.redis.connection.stream.RecordId;
import org.springframework.data.redis.connection.stream.StreamRecords;
import org.springframework.data.redis.connection.stream.StringRecord;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.concurrent.TimeUnit;


/**
 * @author xuyongzheng
 * @since 2021/2/25 10:37
 **/
@Service
@Slf4j
public class RedPacketServiceImpl implements RedPacketService {

    @Autowired
    private RoomInnerService roomInnerService;

    @Autowired
    private PaymentService paymentService;

    @Autowired
    private NoticesService noticesService;

    @Autowired
    private RedPacketMapper redPacketMapper;

    @Autowired
    private RedPacketRecordMapper redPacketRecordsDao;

    @Autowired
    private RedPacketSettingService redPacketSettingService;

    @Autowired
    private PaasService paasService;

    @Autowired
    private RedPacketGenerator algorithm;

    @Autowired
    private InnerPayService innerPayService;

    @Autowired
    private CacheService cacheService;

    @Autowired
    private RedPacketStatServiceImpl statService;

    @Autowired
    private RedPacketRecordService redPacketRecordService;

    @Autowired
    private OrderService orderService;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    Map<String, Integer> validTime = new HashMap<>();

    @Value("${vhall.red-packet.valid-time:30}")
    private Integer vtime;

    @Value("${vhall.pay.fake:false}")
    private Boolean fakePay;

    @Value("${vhall.red-packet.stream-name:rp-stream}")
    public String streamName;

    @Value("${paas.apps.lite.appId}")
    public String appId;
    @Value("${paas.apps.lite.appSecret}")
    public String appSecret;

    @Autowired
    private NotifyService notifyService;

    @Autowired
    @Setter
    private ApplicationEventPublisher applicationEventPublisher;


    @Override
    public RedPacketCreateRspVO create(VssToken token, RedPacketCreateReqVO vo) {
        RedPacketCreateRspVO result = new RedPacketCreateRspVO();
        RedPacketEntity entity = new RedPacketEntity();
        BeanUtils.copyProperties(vo, entity);
        //直接生成一个uuid
        entity.setUuid(IdUtil.fastSimpleUUID());
        entity.setAmount(vo.getAmount());
        // 红包条件
        entity.setCondition(vo.getCondition());

        // 从room获取房间的channelId;
        RoomsEntityDTO room = roomInnerService.getRoomInfo(vo.getRoomId());
        if (null == room) {
            throw new BusinessException(BizErrorCode.EMPTY_ROOM);
        }
        if (StringUtils.isAllEmpty(room.getChannelId())) {
            //52002
            throw new BusinessException(BizErrorCode.COMP_REDPACKET_CHANNEL_ERROR);
        }
        entity.setPushChannel(room.getChannelId());


        String bizOrderNo = "RP" + System.currentTimeMillis();

        entity.setAppId(token.getAppId());
        entity.setAccountId(token.getThirdPartyUserId());
        entity.setTradeNo(bizOrderNo);
        LocalDateTime now = LocalDateTime.now();
        entity.setPayChannel(mapPayChannel(vo.getChannel()));
        entity.setCreatedAt(now);
        entity.setUpdatedAt(now);
        entity.setStartTime(now);

        // 2.写入DB
        int effect = redPacketMapper.insert(entity);
        if (effect <= 0) {
            throw new BusinessException(BizErrorCode.COMP_REDPACKET_CREATE_ERROR);
        }

        // 表情处理
        if (vo.getGiftType() == 2) {
            // 拷贝参数
            BeanUtils.copyProperties(entity, result);
            return buildGifData(result);
        }

        // 3.生成红包明细
        boolean created;
        int amountCent = entity.getAmount().multiply(new BigDecimal(100)).intValue();
        entity = redPacketMapper.selectById(entity.getId());
        if (entity.getType() == 0) {
            created = algorithm.randomSlice(entity.getId(), entity.getGiftType(), amountCent, entity.getNumber());
        } else {
            amountCent = entity.getAmount().multiply(new BigDecimal(100)).multiply(new BigDecimal(entity.getNumber())).intValue();
            created = algorithm.averageSlice(entity.getId(), entity.getGiftType(), amountCent, entity.getNumber());
        }
        if (!created) {
            throw new BusinessException(BizErrorCode.BIZ_HOT);
        }

        // 拷贝参数
        BeanUtils.copyProperties(entity, result);
        if (fakePay) {
            result.setPayStatus(1);
            result.setThirdPartyTradeNo("FAKE" + bizOrderNo);
            result.setPayData("");

            LambdaUpdateWrapper<RedPacketEntity> update = Wrappers.lambdaUpdate();
            update.set(RedPacketEntity::getPayStatus, 1)
                    .eq(RedPacketEntity::getId, entity.getId());
            redPacketMapper.update(null, update);
            // 假支付在这里要调用下这个方法，这样支付组件才会回调我们。
            getPaymentOfFake(bizOrderNo, vo, entity);
            return result;
        }


        // 生成支付信息
        PaymentParamVO payArg = new PaymentParamVO();
        BeanUtils.copyProperties(entity, payArg);
        payArg.setBizOrderNo(bizOrderNo);
        payArg.setTitle("活动红包");
        payArg.setDetail(StringUtils.isAllEmpty(vo.getDescribe()) ? "活动红包" : "活动红包:" + vo.getDescribe());
        payArg.setTotalFee(vo.getAmount());
        payArg.setOpenId(vo.getOpenId());
        payArg.setOptional("red_packet");
        payArg.setUsage("red_packet");
        payArg.setChannel(vo.getChannel());
        payArg.setServiceCode(vo.getServiceCode());
        Object payParma = paymentService.genPayment(payArg);

        result.setPayData(payParma);
        return result;
    }

    /**
     * 假支付的话，去调用支付中心通知他回调我们的/pay-status-set
     *
     * @param entity
     */
    private void getPaymentOfFake(String bizOrderNo, RedPacketCreateReqVO vo, RedPacketEntity entity) {
        // 构建map参数
        Map<String, Object> params = BeanUtil.beanToMap(entity, true, false);
        // 交易信息入缓存，该方法只在测试环境有效
        paymentService.tradeNoInsertCache(params);
        HashMap<String, Object> payParams = new HashMap<>(15);
        payParams.put("channel", vo.getChannel());
        payParams.put("service_code", vo.getServiceCode());
        payParams.put("biz_order_no", bizOrderNo);
        payParams.put("title", "活动红包");
        payParams.put("detail", StringUtils.isAllEmpty(vo.getDescribe()) ? "活动红包" : "活动红包:" + vo.getDescribe());
        payParams.put("total_fee", vo.getAmount());
        payParams.put("open_id", vo.getOpenId());
        payParams.put("optional", "red_packet");
        String now = LocalDateTimeUtil.format(LocalDateTime.now(), DatePattern.NORM_DATETIME_PATTERN);
        payParams.put("app_id", entity.getAppId());
        payParams.put("timestamp", now);
        payParams.put("nonce_str", MD5.create().digestHex("vss_pay_nonce_str_" + DateUtil.currentSeconds() + "_" + RandomUtil.getRandomNum(0, 99999)));
        payParams.put("sign_at", DateUtil.currentSeconds());
        payParams.put("sign", makeSign(payParams));
        notifyService.payNotify(payParams);
    }

    private String makeSign(Map<String, Object> params) {
        params.remove("sign");
        StringBuilder str = new StringBuilder();
        Object[] keyArr = params.keySet().toArray();
        Arrays.sort(keyArr);
        for (Object key : keyArr) {
            String keyStr = (String) key;
            if (params.get(keyStr) != null && !"null".equals(params.get(keyStr))) {
                str.append(key).append(params.get(keyStr));
            }
        }
        str = new StringBuilder(appSecret + str + appSecret);
        return MD5.create().digestHex(str.toString());
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public RedPacketJoinRspVO join(RedPacketJoinVO reqVO, JSONObject unlock) {

        RedPacketJoinRspVO rsp = new RedPacketJoinRspVO();
        //替换red_packet_id
        RedPacketEntity packet = getRowByRedPacketUuid(paasService.getAppId(), reqVO.getRedPacketUuid());

        // 获取红包锁定状态
        checkLockRedPacket(packet, unlock, reqVO.getUserId());

        // 根据source_id分类,存储参与人
        String pkey = RedpacketConstants.COUNT_RED_PACKET_JOIN_BY_RED_PACKET_ID + packet.getId();
        redisTemplate.opsForSet().add(pkey, reqVO.getUserId().toString());

        // 根据source_id分类,存储参与人
        String skey = RedpacketConstants.COUNT_RED_PACKET_JOIN_BY_SOURCE_ID + paasService.getAppId() + "_" + reqVO.getRoomId();
        redisTemplate.opsForSet().add(skey, reqVO.getUserId().toString());

        //统计在线人数
        if (packet.getOnlineUserCount() == 0 && StringUtils.isNotBlank(packet.getPushChannel())) {
            Long count = paasService.getUserOnlineCount(packet.getPushChannel());
            if (count != 0) {
                packet.setOnlineUserCount(count.intValue());
                LambdaUpdateWrapper<RedPacketEntity> update = Wrappers.lambdaUpdate();
                update.eq(RedPacketEntity::getId, packet.getId())
                        .eq(RedPacketEntity::getAppId, paasService.getAppId())
                        .eq(RedPacketEntity::getDeleted, RedpacketConstants.SOFT_DELETE)
                        .set(RedPacketEntity::getOnlineUserCount, count)
                        .set(RedPacketEntity::getUpdatedAt, LocalDateTime.now());
                redPacketMapper.update(null, update);
            }
        }
        BeanUtils.copyProperties(packet, rsp);
        Long count = redisTemplate.opsForSet().size(pkey);
        count = count == null ? 0 : count;
        rsp.setJoinedUserCount(count.intValue());
        RedPacketVO formatRow = formatRow(packet);
        rsp.setRedPacketVO(formatRow);
        return rsp;
    }


    /**
     * 抢红包
     *
     * @param vo
     * @param unlock
     * @param extension
     * @return
     */
    @Override
    public RedPacketGetRspVO snatch(RedPacketJoinVO vo, JSONObject unlock, String extension) {

        RedPacketGetRspVO rsp = new RedPacketGetRspVO();

        Integer userId = vo.getUserId() == null ? VssTokenThreadLocal.getToken().getThirdPartyUserId() : vo.getUserId();

        //替换red_packet_id
        RedPacketEntity packet = getRowByRedPacketUuid(paasService.getAppId(), vo.getRedPacketUuid());
        if (packet == null) {
            throw new BusinessException(BizErrorCode.COMP_REDPACKET_DELETE_ERROR);
        }

        // 判断是否支付成功
        if (packet.getPayStatus() == 0) {
            throw new BusinessException(BizErrorCode.COMP_REDPACKET_DELETE_ERROR);
        }

        // 获取红包锁定状态
        checkLockRedPacket(packet, unlock, userId);


        RedPacketRecordEntity records = new RedPacketRecordEntity();
        records.setRedPacketUuid(packet.getUuid());
        records.setAppId(paasService.getAppId());
        records.setRedPacketId(packet.getId());
        records.setUserId(userId);
        records.setGiftType(packet.getGiftType());

        //附加一个tradeNo, 用于订单入库
        records.setTradeNo(packet.getTradeNo());


        String key = RedpacketConstants.RED_PACKET_USER_BY_APP_ID + paasService.getAppId() + "_" + packet.getId();

        //判断缓存中是否存在该成员
        if (redisTemplate.opsForSet().isMember(key, String.valueOf(userId))) {
            throw new BusinessException(BizErrorCode.COMP_REDPACKET_AGAIN_ERROR);
        }
        String cacheKey = RedpacketConstants.QUEUE_RED_PACKET_PREFIX + packet.getId();

        String popStr = cacheService.popFromQueue(cacheKey, false, null);

        List<Object> list = JSON.parseObject(popStr, List.class);
        if (CollectionUtils.isEmpty(list)) {
            throw new BusinessException(BizErrorCode.COMP_REDPACKET_ZERO_ERROR);
        }
        String roomId = vo.getRoomId();
        records.setRoomId(roomId);
        double da = Double.parseDouble(list.get(0).toString());
        BigDecimal amount = BigDecimal.valueOf(da).setScale(2, RoundingMode.HALF_UP);
        records.setAmount(amount);
        records.setAmountRanking((Integer) list.get(1));
        records.setPercent((Integer) list.get(2));

        // pop有值 更新红包的状态
        cacheService.cacheStat(paasService.getAppId(), packet.getRoomId(), packet.getId(),
                userId.toString(), records.getAmount());

        records.setRedPacketId(packet.getId());
        records.setCreatedAt(LocalDateTime.now());
        records.setUpdatedAt(LocalDateTime.now());
        records.setExtension(extension);

        // 数据添加到缓存
        cacheService.cacheRecords(records);

        // 领取记录添加到queue, 方便查询, 实际没用到
        // cacheService.pushToQueue(records);

        BeanUtils.copyProperties(records, rsp);


        // 发布一条消息  || 2. 写入收入订单
        // redisTemplate.convertAndSend(channelTopic, records.getId().toString());
        // 发送消息
        String msg = JSON.toJSONString(records);
        try {
            StringRecord rmsg = StreamRecords.string(Collections.singletonMap(HttpConstant.DATA, msg))
                    .withStreamKey(streamName);
            RecordId recordId = redisTemplate.opsForStream().add(rmsg);
            log.info("红包记录消息发送成功,[{}],[{}]", recordId, msg);
        } catch (Exception e) {
            // 领取记录添加到queue, 方便查询, 如果stream异常,用于修复数据
            cacheService.pushToQueue(records);
            log.error("红包记录消息发送失败,写入队列[comp:redpacket:queue:record],[" + msg + "]", e);
        }
        // 获取加入房间的用户信息
        RedPacketVO formatRow = formatRow(packet);
        if (formatRow != null) {
            // 获取创建红包人的参会信息
            RoomJoinsEntityDTO joinInfo = roomInnerService.getRoomJoinInfo(packet.getAccountId(), roomId);
            formatRow.setNickname(joinInfo.getNickname());
            formatRow.setAvatar(joinInfo.getAvatar());
        }
        rsp.setRedPacketVO(formatRow);
        // 获取领取人的参会信息
        RoomJoinsEntityDTO roomJoinInfo = roomInnerService.getRoomJoinInfo(userId, roomId);
        rsp.setAvatar(roomJoinInfo.getAvatar());
        rsp.setNickname(roomJoinInfo.getNickname());
        rsp.setStatus(1);

        // 异步调用paas发送消息
        RoomsEntityDTO roomInfo = roomInnerService.getRoomInfo(roomId);
        if (Objects.nonNull(roomInfo) && StringUtils.isNotEmpty(roomInfo.getChannelId())) {
            String messageBody = getMessageBody(rsp);
            applicationEventPublisher.publishEvent(new PaasMessageEvent.Builder(this, roomInfo.getChannelId(), messageBody).build());
        }
        return rsp;
    }

    private String getMessageBody(RedPacketGetRspVO data) {
        JSONObject content = new JSONObject();
        content.put("type", "red_envelope_open_success");
        content.put("room_id", data.getRoomId());
        content.put("red_packet_uuid", data.getRedPacketVO().getUuid());
        content.put("sender_id", data.getRedPacketVO().getAccountId());
        content.put("sender_nickname", data.getRedPacketVO().getNickname());
        content.put("sender_avatar", data.getRedPacketVO().getAvatar());
        content.put("red_packet_describe", data.getRedPacketVO().getDescribe());
        content.put("red_packet_type", data.getRedPacketVO().getType());
        content.put("red_packet_start_time", data.getRedPacketVO().getStartTime());
        content.put("red_packet_number", data.getRedPacketVO().getNumber());
        content.put("red_packet_get_user_count", data.getRedPacketVO().getGetUserCount());
        content.put("red_packet_amount", data.getRedPacketVO().getAmount());
        content.put("red_packet_status", data.getRedPacketVO().getGetUserCount() >= data.getRedPacketVO().getNumber() ? 0 : 1);
        content.put("receiver_id", data.getUserId());
        content.put("receiver_nickname", data.getNickname());
        content.put("receiver_avatar", data.getAvatar());
        content.put("receiver_amount_ranking", data.getAmountRanking());
        content.put("receiver_amount", data.getAmount());
        content.put("receiver_percent", data.getPercent());
        content.put("gift_type", data.getRedPacketVO().getGiftType());
        /*RoomsEntityDTO roomInfo = roomInnerFacade.getRoomInfo(data.getRoomId());
        paasService.sendMessage(roomInfo.getChannelId(), content.toJSONString());*/
        return content.toJSONString();
    }


    @Override
    public RedPacketVO getInfoBySourceId(OverBySourceIdReqVO reqVO) {
        Integer validTime1 = getValidTime(reqVO.getAppId());
        long time = Calendar.getInstance().getTimeInMillis();
        SimpleDateFormat simpleDate = new SimpleDateFormat(CommonConstant.DATETIME_FORMAT);
        String startTime = simpleDate.format(time - validTime1 == 0 ? time : time - (validTime1 * 1000));

        Integer gitType = RedpacketConstants.GIFT_RED_PACKET;
        if (null != reqVO.getGiftType()) {
            gitType = reqVO.getGiftType();
        }

        LambdaQueryWrapper<RedPacketEntity> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(RedPacketEntity::getAppId, reqVO.getAppId())
                .eq(RedPacketEntity::getRoomId, reqVO.getSourceId())
                .eq(RedPacketEntity::getGiftType, gitType)
                .eq(RedPacketEntity::getDeleted, RedpacketConstants.SOFT_DELETE)
                .gt(RedPacketEntity::getStartTime, startTime)
                .last("order by start_time desc limit 1;");
        RedPacketEntity packet = redPacketMapper.selectOne(wrapper);
        if (packet == null) {
            log.warn("查询不到红包，查询参数app_id:{},source_id:{},gift_type:{},start_time:{}",
                    reqVO.getAppId(), reqVO.getSourceId(), gitType, startTime);
            return null;
        }

        return formatRow(packet);
    }


    /**
     * 检查红包锁定状态
     *
     * @param packet 红包实体
     * @param unlock 是否已
     */
    private void checkLockRedPacket(RedPacketEntity packet, JSONObject unlock, Integer thirdPartyUserId) {

        if (packet == null || packet.getId() == null) {
            throw new BusinessException(BizErrorCode.COMP_REDPACKET_DELETE_ERROR);
        }
        // 抢红包条件判断
        // 如果condition!=0 (条件解锁红包) 进行判断
        if (!RedpacketConstants.CONDITION_UNLIMITED.equals(packet.getCondition())) {
            // 如果解锁字段中 包含了红包的 解锁条件, 直接跳过
            if (unlock.containsKey(packet.getCondition().toString())) {
                if (!unlock.getBoolean(packet.getCondition().toString())) {
                    // 不包含解锁条件, 查询缓存,看红包的锁状态(是否运行参加)
                    String key = RedpacketConstants.CACHE_RED_PACKET_UNLOCK_KEY + packet.getAppId() + "_" + packet.getId() + "_" + thirdPartyUserId;
                    String data = redisTemplate.opsForValue().get(key);
                    if (!RedpacketConstants.UNLOCK_VALUE.equals(data)) {
                        throw new BusinessException(BizErrorCode.COMP_REDPACKET_CONDITION_ERROR);
                    }
                }
            } else {
                String key = RedpacketConstants.CACHE_RED_PACKET_UNLOCK_KEY + packet.getAppId() + "_" + packet.getId() + "_" + thirdPartyUserId;
                String data = redisTemplate.opsForValue().get(key);
                if (!RedpacketConstants.UNLOCK_VALUE.equals(data)) {
                    throw new BusinessException(BizErrorCode.COMP_REDPACKET_CONDITION_ERROR);
                }
            }
        }
        if (packet.getStartTime().isAfter(LocalDateTime.now())) {
            // 尚未开始
            throw new BusinessException(BizErrorCode.COMP_REDPACKET_START_ERROR);
        }
        if (packet.getRefundStatus() != 0) {
            // 红包已过期
            throw new BusinessException(BizErrorCode.COMP_REDPACKET_OVERDUE_ERROR);
        }

        // 1.缓存判断
        String detailKey = RedpacketConstants.QUEUE_RED_PACKET_PREFIX + packet.getId();
        Long size = redisTemplate.opsForList().size(detailKey);
        size = size == null ? 0 : size;
        if (size <= 0) {
            // 红包已抢完
            throw new BusinessException(BizErrorCode.COMP_REDPACKET_ZERO_ERROR);
        }


        Integer timeout = 30;
        RedPacketSettingEntity setting = redPacketSettingService.getRowByAppId(packet.getAppId());
        if (setting != null && setting.getValidTimeout() != null && setting.getValidTimeout() > 0) {
            timeout = setting.getValidTimeout();
        }
        if (LocalDateTime.now().isAfter(packet.getStartTime().plusSeconds(timeout.longValue()))) {
            // 抢时间片已过
            throw new BusinessException(BizErrorCode.COMP_REDPACKET_OVERDUE_ERROR);
        }
    }

    @Override
    public GetListRspVO listPacket(GetListReqVO vo) {
        int page = (vo.getPage() != null) ? vo.getPage() : 1;
        int pageSize = (vo.getPageSize() != null) ? vo.getPageSize() : 10;
        int offset = (page - 1) * pageSize;
        vo.setPage(page);
        vo.setSortType(vo.getSortType());
        vo.setPageSize(pageSize);
        if (StringUtils.isBlank(vo.getThirdPartyUserId())) {
            vo.setThirdPartyUserId("");
        }
        GetListRspVO result = new GetListRspVO();
        result.setCount(0);
        LambdaQueryWrapper<RedPacketEntity> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(RedPacketEntity::getAppId, vo.getAppId())
                .eq(StringUtils.isNotBlank(vo.getSourceId()), RedPacketEntity::getRoomId, vo.getSourceId())
                .eq(StringUtils.isNotBlank(vo.getThirdPartyUserId()), RedPacketEntity::getAccountId, vo.getThirdPartyUserId())
                .eq(RedPacketEntity::getDeleted, RedpacketConstants.SOFT_DELETE)
                .eq(vo.getPayStatus() != null, RedPacketEntity::getPayStatus, vo.getPayStatus())
                .eq(vo.getType() != null, RedPacketEntity::getType, vo.getType())
                .eq(vo.getRefundStatus() != null, RedPacketEntity::getRefundStatus, vo.getRefundStatus())
                .eq(vo.getGiftType() != null, RedPacketEntity::getGiftType, vo.getGiftType())
                .ge(vo.getStartTime() != null, RedPacketEntity::getCreatedAt, vo.getStartTime())
                .le(vo.getEndTime() != null, RedPacketEntity::getCreatedAt, vo.getEndTime());
        int total = redPacketMapper.selectCount(wrapper);
        result.setCount(total);
        if (total <= 0) {
            result.setCount(0);
            result.setList(new ArrayList<>());
            result.setPage(page);
            result.setPageSize(pageSize);
            result.setStartTime(LocalDateTime.now());
            result.setEndTime(LocalDateTime.now());
            result.setPayStatus("");
            result.setType(0);
            result.setThirdPartyUserId("");
            return result;
        }
        wrapper.last("order by created_at " + vo.getSortType() + " limit " + offset + "," + pageSize);
        List<RedPacketEntity> packets = redPacketMapper.selectList(wrapper);
        List<RedPacketVO> rows = new ArrayList<>();
        packets.forEach(redPacket -> {
            RedPacketVO row = formatRow(redPacket);
            rows.add(row);
        });
        BeanUtils.copyProperties(vo, result);
        result.setList(rows);
        result.setType(vo.getType());
        result.setPage(page);
        result.setPageSize(pageSize);
        return result;
    }

    @Override
    public RedPacketStatRspVO getStat(RedPacketStatReqVO vo) {
        RedPacketEntity packet = null;
        if (StringUtils.isNotBlank(vo.getRedPacketUuid())) {
            //根据uuid获取一条redPacket数据
            packet = getRowByRedPacketUuid(vo.getAppId(), vo.getRedPacketUuid());
            if (packet == null && vo.getSourceId() == null) {
                throw new BusinessException(BizErrorCode.COMP_REDPACKET_DELETE_ERROR);
            }
        }

        RedPacketStatRspVO result = new RedPacketStatRspVO();
        StatVo stat = new StatVo();

        if (StringUtils.isNotBlank(vo.getSourceId())) {
            stat = statService.statBySourceId(vo.getAppId(), vo.getSourceId(), vo.getPayStatus());
        } else if (StringUtils.isNotBlank(vo.getRedPacketUuid()) && packet != null) {
            stat = statService.statByRedPacketId(vo.getAppId(), packet.getId(), packet);
        } else {
            stat.setJoinedUserCount(0);
            stat.setGetUserCount(0);
            stat.setGetAmount(BigDecimal.ZERO);
        }
        BeanUtils.copyProperties(stat, result);
        return result;
    }


    @Override
    public RedPacketVO getInfo(ReqVo vo) {
        String cache = redisTemplate.opsForValue().get(RedpacketConstants.REDPACKET_INFO + vo.getRedPacketUuid());
        if (StringUtils.isNotBlank(cache)) {
            return formatRow(JSON.parseObject(cache, RedPacketEntity.class));
        }
        RedPacketEntity redPacketEntity = cacheService.cacheAndHadleRedPacket(vo.getRedPacketUuid(), vo.getAppId());
        return formatRow(redPacketEntity);
    }


    @Override
    public Boolean unLock(RedPacketUnLockReqVO vo) {
        RedPacketEntity packet = getRowByRedPacketUuid(vo.getAppId(), vo.getRedPacketUuid());
        if (packet == null) {
            throw new BusinessException(BizErrorCode.COMP_REDPACKET_DELETE_ERROR);
        }
        if (!packet.getCondition().equals(vo.getCondition())) {
            throw new BusinessException(BizErrorCode.COMP_REDPACKET_UNLOCK_ERROR);
        }

        String cacheKey = RedpacketConstants.CACHE_RED_PACKET_UNLOCK_KEY + vo.getAppId() + "_" + packet.getId() + "_" + vo.getThirdPartyUserId();

        // 口令抽奖, 验证口令
        if (RedpacketConstants.CONDITION_PASSWORD.equals(packet.getCondition())) {
            if (!packet.getPassword().equals(vo.getPassword())) {
                throw new BusinessException(BizErrorCode.COMP_REDPACKET_UNLOCK_ERROR);
            }
            redisTemplate.opsForValue().set(cacheKey, RedpacketConstants.UNLOCK_VALUE, 1, TimeUnit.HOURS);
        } else {
            redisTemplate.opsForValue().set(cacheKey, RedpacketConstants.UNLOCK_VALUE, 1, TimeUnit.HOURS);
        }
        return true;
    }

    @Override
    public RedPacketRspVO payStatusSet(PayStatusSetReqVO reqVO) {
        log.info("payStatusSet被调用参数信息为：{}", JSON.toJSONString(reqVO));
        // 判断是否是假支付
        if (fakePay) {
            log.info("当前支付设置是假支付，参数中的支付状态为：{}", reqVO.getPayStatus());
            reqVO.setPayStatus(1);
        }
        LambdaQueryWrapper<RedPacketEntity> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(RedPacketEntity::getAppId, reqVO.getAppId())
                .eq(RedPacketEntity::getTradeNo, reqVO.getTradeNo())
                .eq(RedPacketEntity::getDeleted, RedpacketConstants.NOT_DELETED)
                .last("limit 1");
        RedPacketEntity redPacket = redPacketMapper.selectOne(wrapper);
        if (redPacket == null) {
            log.info("payStatusSet查询结果为null！");
            return null;
        }
        RedPacketEntity update = new RedPacketEntity();
        update.setPayStatus(reqVO.getPayStatus());
        update.setId(redPacket.getId());
        LocalDateTime now = LocalDateTime.now();
        update.setUpdatedAt(now);
        int updateById = redPacketMapper.updateById(update);
        if (updateById == 0) {
            log.info("payStatusSet更新失败！参数为：{}", update);
            return null;
        }
        // 发送消息
        RoomsEntityDTO roomInfo = roomInnerService.getRoomInfo(redPacket.getRoomId());
        RoomJoinsEntityDTO userInfo = roomInnerService.getRoomJoinsByRoomIdAndThirdId(redPacket.getRoomId(), redPacket.getAccountId());
        Map<String, Object> param = new HashMap<>(16);
        param.put("type", "red_envelope_push");
        param.put("room_id", redPacket.getRoomId());
        param.put("red_packet_uuid", redPacket.getUuid());
        param.put("sender_id", redPacket.getAccountId());
        param.put("sender_nickname", userInfo.getNickname());
        param.put("sender_avatar", userInfo.getAvatar());
        param.put("red_packet_describe", redPacket.getDescribe());
        param.put("red_packet_number", redPacket.getNumber());
        param.put("red_packet_amount", redPacket.getAmount());
        param.put("red_packet_type", redPacket.getType());
        param.put("red_packet_start_time", redPacket.getStartTime());
        JSONObject r = paasService.sendMessage(roomInfo.getChannelId(), JSON.toJSONString(param));
        log.info("红包消息发送,result:{}", r.toJSONString());
        // 收入订单
        OrderDetailEntity order = new OrderDetailEntity();
        order.setRoomId(redPacket.getRoomId());
        order.setAmount(redPacket.getAmount());
        order.setSource(0);
        order.setTradeNo(redPacket.getTradeNo());
        order.setAppId(paasService.getAppId());
        order.setChannel(1 == redPacket.getPayChannel() ? 1 : 2);
        order.setAccountId(redPacket.getAccountId());
        order.setStatus(1);
        orderService.create(order);
        log.info("[红包组件]订单-支出创建成功,order_id={}", order.getId());
        // 增加红包聊天消息 此处因为要与php的v3对齐 故现在不发送公告
//        RedPacketNoticeVo notice = new RedPacketNoticeVo();
//        Map<String, Object> conMap = new HashMap<>();
//        conMap.put("room_id", redPacket.getRoomId());
//        conMap.put("red_packet_describe", redPacket.getDescribe());
//        conMap.put("red_status", 1);
//        conMap.put("red_packet_uuid", redPacket.getUuid());
//        conMap.put("type", "redpacket");
//        notice.setContent(JSON.toJSONString(conMap));
//        notice.setRpUuid(redPacket.getUuid());
//        notice.setAccountId(redPacket.getAccountId());
//        notice.setType(1);
//        notice.setRoomId(redPacket.getRoomId());
//        noticeInnerFacade.addRpNotice(notice);
//        Map<String, Object> paramOfNotice = new HashMap<>();
//        paramOfNotice.put("type", "room_announcement");
//        paramOfNotice.put("room_join_id", redPacket.getAccountId());
//        paramOfNotice.put("room_announcement_text", JSON.toJSONString(conMap));
//        paramOfNotice.put("push_time", DateUtil.format(LocalDateTime.now(), DatePattern.NORM_DATETIME_PATTERN));
//        JSONObject rOfNotice = paasService.sendMessage(roomInfo.getChannelId(), JSON.toJSONString(paramOfNotice));
//        log.info("红包广告消息发送,result:{}", rOfNotice.toJSONString());
        RedPacketRspVO rspVO = new RedPacketRspVO();
        BeanUtils.copyProperties(redPacket, rspVO);
        rspVO.setPayStatus(reqVO.getPayStatus());
        rspVO.setUpdatedAt(now);
        return rspVO;
    }

    @Override
    public RedPacketVO getRowByRedPacketUuid(String appId, String redPacketUuid) {
        String cache = redisTemplate.opsForValue().get(RedpacketConstants.REDPACKET_INFO + redPacketUuid);
        if (StringUtils.isNotBlank(cache)) {
            return formatRow(JSON.parseObject(cache, RedPacketEntity.class));
        }
        RedPacketEntity redPacketEntity = cacheService.cacheAndHadleRedPacket(redPacketUuid, appId);
        return formatRow(redPacketEntity);
    }

    @Override
    public RedPacketGetMyInfoRspVO getMyInfo(RedPacketGetMyInfoReqVO vo) {
        RedPacketGetMyInfoRspVO result = new RedPacketGetMyInfoRspVO();
        // 1、获取红包详情
        Integer thirdId;
        VssToken token = VssTokenThreadLocal.getToken();
        if (vo.getThirdPartyUserId() != null) {
            thirdId = vo.getThirdPartyUserId();
        } else {
            thirdId = token.getThirdPartyUserId();
        }
        RoomJoinsEntityDTO userInfo = roomInnerService.getRoomJoinsByRoomIdAndThirdId(vo.getRoomId(), thirdId);
        ReqVo rv = new ReqVo();
        rv.setRedPacketUuid(vo.getRedPacketUuid());
        rv.setAppId(paasService.getAppId());
        RedPacketVO redPacketInfo = getInfo(rv);
        RedPacketNewVO newRedPacketInfo = new RedPacketNewVO();
        if (redPacketInfo != null) {
            BeanUtils.copyProperties(redPacketInfo, newRedPacketInfo);
            newRedPacketInfo.setNickName(StringUtils.isNotEmpty(userInfo.getNickname()) ? userInfo.getNickname() : "");
            newRedPacketInfo.setAvatar(StringUtils.isNotEmpty(userInfo.getAvatar()) ? userInfo.getAvatar() : "");
        }
        RedPacketRecordReqVO param = new RedPacketRecordReqVO();
        param.setAppId(paasService.getAppId());
        param.setRedPacketUuid(vo.getRedPacketUuid());
        param.setThirdPartyUserId(thirdId.toString());
        RedPacketRecordEntity record = redPacketRecordService.getRecord(param);

        BeanUtils.copyProperties(newRedPacketInfo, result);
        int status = 0;
        if (record != null) {
            BeanUtils.copyProperties(record, result);
            status = 1;
        } else {
            result.setRedPacketId(redPacketInfo == null ? null : redPacketInfo.getId());
            result.setRedPacketUuid(redPacketInfo == null ? null : redPacketInfo.getUuid());
            result.setUserId(vo.getThirdPartyUserId());
        }

        result.setNickName(StringUtils.isNotEmpty(userInfo.getNickname()) ? userInfo.getNickname() : "");
        result.setAvatar(StringUtils.isNotEmpty(userInfo.getAvatar()) ? userInfo.getAvatar() : "");
        result.setStatus(status);
        result.setRedPacket(newRedPacketInfo);
        return result;
    }

    @Override
    public RedPacketGetLastInfoRspVO getLastInfo(RedPacketGetLastInfoReqVO vo) {
        RedPacketGetLastInfoRspVO result = new RedPacketGetLastInfoRspVO();
        GetListReqVO listParam = new GetListReqVO();
        listParam.setAppId(paasService.getAppId());
        listParam.setSourceId(vo.getRoomId());
        listParam.setPage(1);
        listParam.setPageSize(1);
        listParam.setSortType("desc");
        listParam.setPayStatus(1);
        GetListRspVO listResult = listPacket(listParam);
        List list = listResult.getList();
        if (CollectionUtils.isNotEmpty(list)) {
            RedPacketVO data = (RedPacketVO) list.get(0);
            RoomJoinsEntityDTO userInfo = new RoomJoinsEntityDTO();
            if (data != null) {
                BeanUtils.copyProperties(data, result);
                userInfo = roomInnerService.getRoomJoinsByRoomIdAndThirdId(data.getRoomId(), data.getAccountId());
            }
            result.setNickName(StringUtils.isNotEmpty(userInfo.getNickname()) ? userInfo.getNickname() : "");
            result.setAvatar(StringUtils.isNotEmpty(userInfo.getAvatar()) ? userInfo.getAvatar() : "");
            if (Objects.nonNull(data) && data.getValidTime() != null && data.getValidTime() > 0 && data.getRefundStatus() == null) {
                result.setStatus(1);
            } else {
                result.setStatus(2);
            }
        } else {
            result.setStatus(0);
        }
        return result;
    }

    @Override
    public RedPacketGetRecordsRspVO getRecords(RedPacketGetRecordsReqVO vo) {
        RedPacketGetRecordsRspVO result = new RedPacketGetRecordsRspVO();
        result.setPage(vo.getPage());
        result.setPageSize(vo.getPageSize());
        RedPacketRecordsReqVO recordParam = new RedPacketRecordsReqVO();
        recordParam.setAppId(paasService.getAppId());
        recordParam.setRedPacketUuid(vo.getRedPacketUuid());
        recordParam.setPage(vo.getPage());
        recordParam.setPageSize(vo.getPageSize());
        recordParam.setOrder("created_at");
        RedPacketRecordsRspVO resultOfrecords = redPacketRecordService.getRecords(recordParam);
        List records = resultOfrecords.getList();
        if (CollectionUtils.isNotEmpty(records)) {
            List<Integer> accountIds = new ArrayList<>();
            for (Object o : records) {
                RedPacketRecordEntity single = (RedPacketRecordEntity) o;
                accountIds.add(single.getUserId());
            }
            RedPacketRecordEntity first = (RedPacketRecordEntity) records.get(0);
            String roomId = StringUtils.isNotEmpty(first.getRoomId()) ? first.getRoomId() : "";
            List<RoomJoinsEntityDTO> userInfos = roomInnerService.getRoomInfoByAccountIds(roomId, JsonUtil.toJsonString(accountIds));

            List<RedPacketGetRecordsNewVO> newRecords = new ArrayList<>();
            for (Object o : records) {
                RedPacketRecordEntity single = (RedPacketRecordEntity) o;
                RedPacketGetRecordsNewVO newSingle = new RedPacketGetRecordsNewVO();
                BeanUtils.copyProperties(single, newSingle);
                for (RoomJoinsEntityDTO userSingle : userInfos) {
                    if (userSingle.getAccountId().equals(newSingle.getUserId())) {
                        newSingle.setNickName(userSingle.getNickname());
                        newSingle.setAvatar(userSingle.getAvatar());
                    }
                }
                newRecords.add(newSingle);
            }
            result.setList(newRecords);
            result.setCount(resultOfrecords.getCount());

            ReqVo rv = new ReqVo();
            rv.setRedPacketUuid(vo.getRedPacketUuid());
            rv.setAppId(paasService.getAppId());
            RedPacketVO redPacketInfo = getInfo(rv);
            RedPacketNewVO newRedPacketInfo = new RedPacketNewVO();
            if (redPacketInfo != null) {
                RoomJoinsEntityDTO userInfo = roomInnerService.getRoomJoinsByRoomIdAndThirdId(vo.getRoomId(), redPacketInfo.getAccountId());
                BeanUtils.copyProperties(redPacketInfo, newRedPacketInfo);
                newRedPacketInfo.setNickName(StringUtils.isNotEmpty(userInfo.getNickname()) ? userInfo.getNickname() : "");
                newRedPacketInfo.setAvatar(StringUtils.isNotEmpty(userInfo.getAvatar()) ? userInfo.getAvatar() : "");
            }
            result.setRedPacket(newRedPacketInfo);
        } else {
            result.setList(new ArrayList<>());
            result.setCount(0);
        }
        return result;
    }


    /**
     * 格式化, 红包数据
     *
     * @param packet 红包数据
     */
    private RedPacketVO formatRow(RedPacketEntity packet) {

        RedPacketVO dto = new RedPacketVO();
        if (null == packet) {
            log.error("格式化响应信息，红包不存在");
            return null;
        }
        Integer vtime = getValidTime(packet.getAppId());
        long nowSeconds = LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"));
        long startTimeSeconds = packet.getStartTime().toEpochSecond(ZoneOffset.of("+8"));
        long time = startTimeSeconds - nowSeconds;
        BeanUtils.copyProperties(packet, dto);
        //开始时间比当前时间晚（还未开始）
        if (time > 0) {
            dto.setTime(time);
            dto.setValidTime(time + vtime);
        } else {
            //开始了
            dto.setTime(0L);
            dto.setValidTime(time + vtime);
        }

        if (packet.getGiftType() == 2) {
            dto.setAmount(packet.getAmount());
            dto.setGetAmount(packet.getGetAmount());
        }

        StatVo stat = statService.statByRedPacketId(packet.getAppId(), packet.getId(), dto);
        dto.setJoinedUserCount(stat.getJoinedUserCount());
        dto.setGetAmount(stat.getGetAmount());
        dto.setGetUserCount(stat.getGetUserCount());
        dto.setPushCount(stat.getPushCount());
        dto.setPushAmount(stat.getPushAmount());
        dto.setId(packet.getId());

        return dto;
    }

    private Integer getValidTime(String appId) {
        if (validTime.containsKey(appId)) {
            return validTime.get(appId);
        }
        if (StringUtils.isEmpty(appId)) {
            validTime.put(appId, vtime);
            return vtime;
        }
        RedPacketSettingEntity setting = redPacketSettingService.getRowByAppId(appId);

        if (null != setting && setting.getValidTimeout() > 0) {
            validTime.put(appId, setting.getValidTimeout());
        } else {
            validTime.put(appId, vtime);
        }
        return validTime.get(appId);
    }

    private RedPacketCreateRspVO buildGifData(RedPacketCreateRspVO data) {
        RoomJoinsEntityDTO joins = roomInnerService.getRoomJoins(data.getAccountId(), data.getRoomId());
        data.setNickname(joins.getNickname());
        data.setAvatar(joins.getAvatar());

        // 发送公告// 只入库
        RedPacketNoticeVo notice = new RedPacketNoticeVo();
        Map<String, Object> conMap = new HashMap<>(4);
        conMap.put("room_id", data.getRoomId());
        conMap.put("red_packet_describe", data.getDescribe());
        conMap.put("red_status", 1);
        conMap.put("red_packet_uuid", data.getUuid());
        notice.setContent(JSON.toJSONString(conMap));
        notice.setRpUuid(data.getUuid());
        notice.setAccountId(data.getAccountId());
        notice.setType(1);
        notice.setRoomId(data.getRoomId());
        // 此处因为要与php的v3对齐 故现在不发送公告
        // noticeInnerFacade.addRpNotice(notice);

        // 发送消息
        sendMessage(data);
        return data;
    }

    private void sendMessage(RedPacketCreateRspVO data) {
        Map<String, Object> param = new HashMap<>(16);
        param.put("type", "gift_packet_push");
        param.put("room_id", data.getRoomId());
        param.put("red_packet_uuid", data.getUuid());
        param.put("sender_id", data.getAccountId());
        param.put("sender_nickname", data.getNickname());
        param.put("sender_avatar", data.getAvatar());
        param.put("red_packet_describe", data.getDescribe());
        param.put("red_packet_number", data.getNumber());
        param.put("red_packet_amount", data.getAmount());
        param.put("red_packet_type", data.getType());
        param.put("red_packet_start_time", data.getStartTime());

        JSONObject r = paasService.sendMessage(data.getPushChannel(), JSON.toJSONString(param));
        log.info("礼包消息发送,result:{}", r.toJSONString());
    }

    private int mapPayChannel(String channel) {
        switch (channel) {
            case "WEIXIN":
                return 1;
            case "ALIPAY":
                return 2;
            default:
                return 0;
        }
    }

}